#include "ds18b20.h"

static DS18B20_init_t ds18b20_cb;

#define DS18B20_OutPut_Mode()   ds18b20_cb.out_mode_cb()
#define DS18B20_InPut_Mode()    ds18b20_cb.in_mode_cb()

#define DS18B20_Out(n)          ds18b20_cb.out_cb(n)
#define DS18B20_In()            ds18b20_cb.in_cb()

#define DS18B20_Delay_us(n)     ds18b20_cb.delay_us_cb(n)

float tempbuf1[5] = {0};
float tempbuf2[5] = {0};

uint8_t ram[9] = {0};
/**
  * @brief    发送复位信号
  * @param    none
  * @retval   none
*/
static void DS18B20_Send_Reset_Single(void)
{
	DS18B20_OutPut_Mode();
    
    /* 拉低总线 480 - 960 us*/
	DS18B20_Out(0);
	DS18B20_Delay_us(750);
    
    /* 释放总线 15 - 60 us */
	DS18B20_Out(1);
	DS18B20_Delay_us(15);
}

/**
  * @brief    检测DS18B20存在脉冲
  * @param    none
  * @retval   0 DS18B20设备正常
  * @retval   -1  DS18B20设备响应复位信号失败
  * @retval   -2  DS18B20设备释放总线失败
*/
static int DS18B20_Check_Ready_Single(void)
{
	uint8_t cnt = 0;
	/* 1.检测存在脉冲 */
	DS18B20_InPut_Mode();
    
    //等待DS18B20 拉低总线 （60~240 us 响应复位信号）
	while (DS18B20_In() && cnt < 240) {
		DS18B20_Delay_us(1);
		cnt++;
	}
	if (cnt >= 240) {
        return -1;
    }
    
	/* 2.检测DS18B20是否释放总线 */	
	cnt = 0;
	DS18B20_InPut_Mode();
    
    //判断DS18B20是否释放总线（60~240 us 响应复位信号之后会释放总线）
	while ((!DS18B20_In()) && cnt<240) {
		DS18B20_Delay_us(1);
		cnt++;
	}

	if (cnt >= 240) {
        return -2;
    } else {
        return 0;
    }
}

/**
  * @brief    检测DS18B20设备是否正常
  * @param    none
  * @retval   0 DS18B20设备正常
  * @retval   1  DS18B20设备响应复位信号失败
  * @retval   2  DS18B20设备释放总线失败
*/
static int DS18B20_Check_Device(void)
{
    /*1.主机发送复位信号*/
	DS18B20_Send_Reset_Single();
    
    /*2.检测存在脉冲*/
	return DS18B20_Check_Ready_Single();
}

/**
  * @brief    DS18B20初始化
  * @param    none
  * @retval   0:OK, -1 or -2 fail
*/
int DS18B20_Init(DS18B20_init_t init_cb)
{
	int ret = 0;
	memset(&ds18b20_cb, 0, sizeof(DS18B20_init_t));
	memmove(&ds18b20_cb, &init_cb, sizeof(DS18B20_init_t));

	if ((ds18b20_cb.out_cb == NULL)
		|| (ds18b20_cb.in_cb == NULL)
		|| (ds18b20_cb.out_mode_cb == NULL)
		|| (ds18b20_cb.in_mode_cb == NULL)
		|| (ds18b20_cb.delay_us_cb == NULL)) {
            printf("回调函数禁止为NULL\n");
			return -1;
	}

	/*2.检测DS18B20设备是否正常*/
	switch (DS18B20_Check_Device()) {
		case 0:	
            printf("DS18B20_Init OK!\n");
			ret = 0;
            break;
		case -1:
            printf("DS18B20设备响应复位信号失败！\n");
			ret = -1;
            break;
		case -2:
            printf("DS18B20设备释放总线失败！\n");
			ret = -2;
            break;
	}

	return ret;
}

/**
  * @brief    向DS18B20写一个字节
  * @param    cmd 要写入的字节
  * @retval   none
*/
static uint8_t DS18B20_Write_Byte(uint8_t cmd)
{
	uint8_t i = 0;
    
    /* 1. 设置总线为输出模式 */
	DS18B20_OutPut_Mode();
    
    /* 2. 发送数据，低位在前 */
	for (i = 0; i < 8; i++) {
		DS18B20_Out(0);
		DS18B20_Delay_us(2);  
		DS18B20_Out(cmd & 0x01);
		DS18B20_Delay_us(60);
		DS18B20_Out(1);
		cmd >>= 1;
		DS18B20_Delay_us(2);
	}
    
	return 0;
}
 
/**
  * @brief    从DS18B20读一个字节
  * @param    none
  * @retval   读取到的一个字节数据
*/
uint8_t DS18B20_Read_Byte(void)
{
	uint8_t i = 0;
    uint8_t data = 0;
    
    /* 读取数据 */
	for (i  =0; i < 8; i++)	{
		DS18B20_OutPut_Mode();
		DS18B20_Out(0);  
		DS18B20_Delay_us(2);
		DS18B20_Out(1);
        
		DS18B20_InPut_Mode();
		DS18B20_Delay_us(10);
		data >>= 1 ;
		if (DS18B20_In()) {
            data |= 0x80;
        }
        
		DS18B20_Delay_us(60);
		DS18B20_Out(1);
	}
    
	return data;
}

uint8_t DS18B20_Crc(uint8_t *addr, uint8_t len)
{
    uint8_t crc = 0, inbyte, i, mix;
 
    while (len--)
    {
        // inbyte 存储当前参与计算的新字节
        inbyte = *addr++;
 
        for (i = 8; i; i--) 
        {
            // 将新字节与CRC从低位到高位, 依次做异或运算, 每次运算完CRC右移一位
            // 如果运算结果值为1, 则将CRC与 1000 1100 作异或
            // 第3,4位代表流程图中的异或运算, 第7位其实就是运算结果移入的1
            mix = (crc ^ inbyte) & 0x01;
            crc >>= 1;
            if (mix) 
            {
                crc ^= 0x8C;
            }
            inbyte >>= 1;
        }
    }
    return crc;
}

//精度配置
int DS18B20_Resolution_Config(DS18B20_Resolution_e resolution)
{
	int ret = 0;
	ret = DS18B20_Check_Device();
	if (ret) {
		return -1;
	}
	DS18B20_Write_Byte(0xcc);
	DS18B20_Write_Byte(0x4e);//写暂存寄存器命令
	DS18B20_Write_Byte(0x7f);//写过温温度报警寄存器
	DS18B20_Write_Byte(0x00);//写低温温度报警寄存器
	DS18B20_Write_Byte(resolution);//写配置寄存器

	return 0;
}

float DS18B20_Filter_Wave(float temp)
{
	int i = 0, j = 0;
	float a = 0;

	for (i = 0; i < sizeof(tempbuf1) / sizeof(float); i++) {
		a += tempbuf1[i];
	}
	
	if (a == 0) {
		for (i = 0; i < sizeof(tempbuf1) / sizeof(float); i++) {
			tempbuf1[i] = temp;
		}
		return temp;
	} else {
		memmove(tempbuf1, &tempbuf1[1], (sizeof(tempbuf1) - sizeof(float)));
		tempbuf1[(sizeof(tempbuf1) / sizeof(float)) - 1] = temp;
		memmove(tempbuf2, tempbuf1, sizeof(tempbuf2));
	}

	for (i = 0; i < sizeof(tempbuf2) / sizeof(float); i++)
	{
		for (j = 0; j < (sizeof(tempbuf2) / sizeof(float)) - 1; j++) {
			if (tempbuf2[j] > tempbuf2[j + 1]) {
				a = tempbuf2[j];
				tempbuf2[j] = tempbuf2[j + 1];
				tempbuf2[j + 1] = a;
			}
		}
	}

	return tempbuf2[((sizeof(tempbuf2) / sizeof(float)) / 2)];
}

/**
  * @brief    从DS18B20读取一次数据
  * @param[out] temp_o 读取到的温度数据
  * @retval   0: Read success; <0:读取错误
  * @note     适用于总线上只有一个DS18B20的情况
*/
int DS18B20_Read_Temperature(float *temp_o)
{
	int ret = 0;
	int i = 0;
	int flag = 0;
    uint16_t temp = 0;
    uint8_t  temp_H, temp_L;
	float realtemp = 0;

    ret = DS18B20_Check_Device();
	if (ret) {
		return -1;
	}

    DS18B20_Write_Byte(0xCC); //跳过ROM序列检测
    DS18B20_Write_Byte(0x44); //启动一次温度转换
    
    //等待温度转换完成
    while (DS18B20_Read_Byte() != 0xFF);
    
    ret = DS18B20_Check_Device();   //必须，不能省略
	if (ret) {
		return -2;
	}
    
    DS18B20_Write_Byte(0xCC); //跳过ROM序列检测
    DS18B20_Write_Byte(0xBE); //读取温度

    // temp_L = DS18B20_Read_Byte(); //读取的温度低位数据
    // temp_H = DS18B20_Read_Byte(); //读取的温度高位数据

	for (i = 0; i < 9; i++)
	{
		ram[i] = DS18B20_Read_Byte();
	}

	temp_L = ram[0];
	temp_H = ram[1];

	// ret = DS18B20_Crc(ram, 8);
	// if (ram[8] != ret) {
	// 	printf("CRC check failed\n");
	// 	return -99;
	// }

	// printf("RAM:");
	// for (i = 0; i < 9; i++)
	// {
	// 	printf("%02X ", ram[i]);
	// }
	// printf("CRC:%02X\n", ret);

	temp = temp_H << 8 | temp_L; 
	
	if (temp == 0xffff) {
		return -3;
	}

	if (temp > 0xF800) {
		temp -= 1;
		temp = ~temp;
		flag = -1;
	} else {
		flag = 1;
	}

	temp_L = temp & 0X000F;
	temp_H = ((temp & 0x0FFF) >> 4);

	realtemp = (temp_H + temp_L * 0.0625) * flag;
	// printf("T:%.4f\n", realtemp);
	*temp_o = DS18B20_Filter_Wave(realtemp);
	return 0;//返回温度值
}
